"""
Defines ObjectMgrBase
"""

import os
import time
import copy

from panda3d.core import ConfigVariableString, Filename, Mat4, NodePath
from direct.actor.Actor import Actor
from direct.showbase.PythonUtil import Functor
from direct.task import Task
from direct.task.TaskManagerGlobal import taskMgr
from .ActionMgr import ActionTransformObj, ActionUpdateObjectProp
from . import ObjectGlobals as OG


# python wrapper around a panda.NodePath object
class PythonNodePath(NodePath):
    def __init__(self,node):
        NodePath.__init__(self, node)


class ObjectMgrBase:
    """ ObjectMgr will create, manage, update objects in the scene """

    def __init__(self, editor):
        self.editor = editor

        # main obj repository of objects in the scene
        self.objects = {}
        self.npIndex = {}
        self.saveData = []
        self.objectsLastXform = {}

        self.lastUid = ''
        self.lastUidMode = 0
        self.currNodePath = None
        self.currLiveNP = None

        self.Actor = []
        self.findActors(base.render)
        self.Nodes = []
        self.findNodes(base.render)

    def reset(self):
        base.direct.deselectAllCB()

        for id in list(self.objects.keys()):
            try:
                self.objects[id][OG.OBJ_NP].removeNode()
            except Exception:
                pass
            del self.objects[id]

        for np in list(self.npIndex.keys()):
            del self.npIndex[np]

        self.objects = {}
        self.npIndex = {}
        self.saveData = []
        self.Actor = []
        self.Nodes = []

    def genUniqueId(self):
        # [gjeon] to solve the problem of unproper $USERNAME
        userId = os.path.basename(os.path.expandvars('$USERNAME'))
        if userId == '':
            userId = ConfigVariableString("le-user-id").value
        if userId == '':
            userId = 'unknown'
        newUid = str(time.time()) + userId
        # prevent duplicates from being generated in the same frame (this can
        # happen when creating several new objects at once)
        if self.lastUid == newUid:
            # append a value to the end to uniquify the id
            newUid = newUid + str(self.lastUidMod)
            self.lastUidMod = self.lastUidMod + 1
        else:
            self.lastUid = newUid
            self.lastUidMod = 0
        return newUid

    def addNewCurveFromFile(self, curveInfo, degree, uid=None, parent=None, fSelectObject=True, nodePath=None):
        """ function to add new curve to the scene from file"""
        curve = []
        curveControl = []

        #transfer the curve information from simple positions into control nodes
        for item in curveInfo:
            controler = base.render.attachNewNode("controler")
            controler = base.loader.loadModel('models/misc/smiley')
            controlerPathname = f'controler{item[0]}'
            controler.setName(controlerPathname)
            controler.setPos(item[1])
            controler.setColor(0, 0, 0, 1)
            controler.setScale(0.2)
            controler.reparentTo(base.render)
            controler.setTag('OBJRoot', '1')
            controler.setTag('Controller', '1')
            curve.append((None, item[1]))
            curveControl.append((item[0], controler))

        self.editor.curveEditor.degree = degree
        self.editor.curveEditor.ropeUpdate(curve)
        #add new curve to the scene
        curveObjNP = self.addNewCurve(curveControl, degree, uid, parent, fSelectObject, nodePath=self.editor.curveEditor.currentRope)
        curveObj = self.findObjectByNodePath(curveObjNP)
        self.editor.objectMgr.updateObjectPropValue(curveObj, 'Degree', degree, fSelectObject=False, fUndo=False)

        for item in curveControl:
            item[1].reparentTo(curveObjNP)
            item[1].hide()

        curveControl = []
        curve = []
        self.editor.curveEditor.currentRope = None

        return curveObjNP

    def addNewCurve(self, curveInfo, degree, uid=None, parent=None, fSelectObject=True, nodePath=None):
        """ function to add new curve to the scene"""
        if parent is None:
            parent = self.editor.NPParent

        if uid is None:
            uid = self.genUniqueId()

        if self.editor:
            objDef = self.editor.objectPalette.findItem('__Curve__')

        if nodePath is None:
            # we need to create curve
            # and then create newobj with newly created curve
            pass
        else:
            newobj = nodePath

        newobj.reparentTo(parent)
        newobj.setTag('OBJRoot', '1')

        # populate obj data using default values
        properties = {}
        for key in objDef.properties.keys():
            properties[key] = objDef.properties[key][OG.PROP_DEFAULT]

        properties['Degree'] = degree
        properties['curveInfo'] = curveInfo

        # insert obj data to main repository
        self.objects[uid] = [uid, newobj, objDef, None, None, properties, (1,1,1,1)]
        self.npIndex[NodePath(newobj)] = uid

        if self.editor:
            if fSelectObject:
                self.editor.select(newobj, fUndo=0)
            self.editor.ui.sceneGraphUI.add(newobj, parent)
            self.editor.fNeedToSave = True

        return newobj

    def addNewObject(self, typeName, uid = None, model = None, parent=None, anim = None, fSelectObject=True, nodePath=None, nameStr=None):
        """ function to add new obj to the scene """
        if parent is None:
            parent = self.editor.NPParent

        if uid is None:
            uid = self.genUniqueId()

        if self.editor:
            objDef = self.editor.objectPalette.findItem(typeName)
            if objDef is None:
                objDef = self.editor.protoPalette.findItem(typeName)
        else: # when loaded outside of LE
            objDef = base.objectPalette.findItem(typeName)
            if objDef is None:
                objDef = base.protoPalette.findItem(typeName)
        newobj = None
        if objDef and not isinstance(objDef, dict):
            if not hasattr(objDef, 'createFunction'):
                return newobj
            if nodePath is None:
                if objDef.createFunction:
                    funcName = objDef.createFunction[OG.FUNC_NAME]
                    funcArgs = copy.deepcopy(objDef.createFunction[OG.FUNC_ARGS])

                    for pair in list(funcArgs.items()):
                        if pair[1] == OG.ARG_NAME:
                            funcArgs[pair[0]] = nameStr
                        elif pair[1] == OG.ARG_PARENT:
                            funcArgs[pair[0]] = parent

                    if isinstance(funcName, str):
                        if funcName.startswith('.'):
                            # when it's using default objectHandler
                            if self.editor:
                                func = Functor(getattr(self.editor, "objectHandler%s"%funcName))
                            else: # when loaded outside of LE
                                func = Functor(getattr(base, "objectHandler%s"%funcName))
                        else:
                            # when it's not using default objectHandler, whole name of the handling obj
                            # should be included in function name
                            func = Functor(eval(funcName))
                    else:
                        func = funcName
                    # create new obj using function and keyword arguments defined in ObjectPalette
                    newobj = func(**funcArgs)
                elif objDef.actor:
                    if model is None:
                        model = objDef.model
                    try:
                        newobj = Actor(model)
                    except Exception:
                        newobj = Actor(Filename.fromOsSpecific(model).getFullpath())
                    if hasattr(objDef, 'animDict') and objDef.animDict != {}:
                        objDef.anims = objDef.animDict.get(model)

                elif objDef.model is not None:
                    # since this obj is simple model let's load the model
                    if model is None:
                        model = objDef.model
                    try:
                        newobjModel = base.loader.loadModel(model)
                    except Exception:
                        newobjModel = base.loader.loadModel(Filename.fromOsSpecific(model).getFullpath(), okMissing=True)
                    if newobjModel:
                        self.flatten(newobjModel, model, objDef, uid)
                        newobj = PythonNodePath(newobjModel)
                    else:
                        newobj = None

                else:
                    newobj = hidden.attachNewNode(objDef.name)
            else:
                newobj = nodePath

            i = 0
            for i in range(len(objDef.anims)):
                animFile = objDef.anims[i]
                # load new anim
                animName = os.path.basename(animFile)
                if i < len(objDef.animNames):
                    animName = objDef.animNames[i]
                newAnim = newobj.loadAnims({animName:animFile})

                if anim:
                    if anim == animFile:
                        newobj.loop(animName)
                else:
                    if i == 0:
                        anim = animFile
                        newobj.loop(animName)

            if newobj is None:
                return None

            newobj.reparentTo(parent)
            newobj.setTag('OBJRoot', '1')

            # populate obj data using default values
            properties = {}
            for key in objDef.properties.keys():
                properties[key] = objDef.properties[key][OG.PROP_DEFAULT]

            # insert obj data to main repository
            self.objects[uid] = [uid, newobj, objDef, model, anim, properties, (1,1,1,1)]
            self.npIndex[NodePath(newobj)] = uid

            if self.editor:
                if fSelectObject:
                    self.editor.select(newobj, fUndo=0)
                self.editor.ui.sceneGraphUI.add(newobj, parent)
                self.editor.fNeedToSave = True
        return newobj

    def removeObjectById(self, uid):
        obj = self.findObjectById(uid)
        nodePath = obj[OG.OBJ_NP]

        for i in range(0, len(self.Actor)):
            if self.Actor[i] == obj:
                del self.Actor[i]
                break
        for i in range(0, len(self.Nodes)):
            if self.Nodes[i][OG.OBJ_UID] == uid:
                del self.Nodes[i]
                break
        self.editor.animMgr.removeAnimInfo(obj[OG.OBJ_UID])

        del self.objects[uid]
        del self.npIndex[nodePath]

        # remove children also
        for child in nodePath.getChildren():
            if child.hasTag('OBJRoot'):
                self.removeObjectByNodePath(child)
        nodePath.remove()

        self.editor.fNeedToSave = True

    def removeObjectByNodePath(self, nodePath):
        uid = self.npIndex.get(nodePath)
        if uid:
            for i in range(0, len(self.Actor)):
                if self.Actor[i][OG.OBJ_UID] == uid:
                    del self.Actor[i]
                    break
            for i in range(0, len(self.Nodes)):
                if self.Nodes[i][OG.OBJ_UID] == uid:
                    del self.Nodes[i]
                    break
            self.editor.animMgr.removeAnimInfo(uid)

            del self.objects[uid]
            del self.npIndex[nodePath]

        # remove children also
        for child in nodePath.getChildren():
            if child.hasTag('OBJRoot'):
                self.removeObjectByNodePath(child)
        self.editor.fNeedToSave = True

    def findObjectById(self, uid):
        return self.objects.get(uid)

    def findObjectByNodePath(self, nodePath):
        uid = self.npIndex.get(NodePath(nodePath))
        if uid is None:
            return None
        else:
            return self.objects[uid]

    def findObjectByNodePathBelow(self, nodePath):
        for ancestor in nodePath.getAncestors():
            if ancestor.hasTag('OBJRoot'):
                return self.findObjectByNodePath(ancestor)

        return None

    def findObjectsByTypeName(self, typeName):
        results = []
        for uid in self.objects.keys():
            obj = self.objects[uid]
            if obj[OG.OBJ_DEF].name == typeName:
                results.append(obj)

        return results

    def deselectAll(self):
        self.currNodePath = None
        taskMgr.remove('_le_updateObjectUITask')
        self.editor.ui.objectPropertyUI.clearPropUI()
        self.editor.ui.sceneGraphUI.tree.UnselectAll()

    def selectObject(self, nodePath, fLEPane=0):
        obj = self.findObjectByNodePath(nodePath)
        if obj is None:
            return
        self.selectObjectCB(obj, fLEPane)

    def selectObjectCB(self, obj, fLEPane):
        self.currNodePath = obj[OG.OBJ_NP]
        self.objectsLastXform[obj[OG.OBJ_UID]] = Mat4(self.currNodePath.getMat())
        # [gjeon] to connect transform UI with nodepath's transform
        self.spawnUpdateObjectUITask()
        self.updateObjectPropertyUI(obj)
        #import pdb;pdb.set_trace()
        if fLEPane == 0:
            self.editor.ui.sceneGraphUI.select(obj[OG.OBJ_UID])

        if not obj[OG.OBJ_DEF].movable:
            if base.direct.widget.fActive:
                base.direct.widget.toggleWidget()

    def updateObjectPropertyUI(self, obj):
        objDef = obj[OG.OBJ_DEF]
        objProp = obj[OG.OBJ_PROP]
        self.editor.ui.objectPropertyUI.updateProps(obj, objDef.movable)
        self.editor.fNeedToSave = True

    def onEnterObjectPropUI(self, event):
        taskMgr.remove('_le_updateObjectUITask')
        self.editor.ui.bindKeyEvents(False)

    def onLeaveObjectPropUI(self, event):
        self.spawnUpdateObjectUITask()
        self.editor.ui.bindKeyEvents(True)

    def spawnUpdateObjectUITask(self):
        if self.currNodePath is None:
            return

        taskMgr.remove('_le_updateObjectUITask')
        t = Task.Task(self.updateObjectUITask)
        t.np = self.currNodePath
        taskMgr.add(t, '_le_updateObjectUITask')

    def updateObjectUITask(self, state):
        self.editor.ui.objectPropertyUI.propX.setValue(state.np.getX())
        self.editor.ui.objectPropertyUI.propY.setValue(state.np.getY())
        self.editor.ui.objectPropertyUI.propZ.setValue(state.np.getZ())

        h = state.np.getH()
        while h < 0:
            h = h + 360.0

        while h > 360:
            h = h - 360.0

        p = state.np.getP()
        while p < 0:
            p = p + 360.0

        while p > 360:
            p = p - 360.0

        r = state.np.getR()
        while r < 0:
            r = r + 360.0

        while r > 360:
            r = r - 360.0

        self.editor.ui.objectPropertyUI.propH.setValue(h)
        self.editor.ui.objectPropertyUI.propP.setValue(p)
        self.editor.ui.objectPropertyUI.propR.setValue(r)

        self.editor.ui.objectPropertyUI.propSX.setValue(state.np.getSx())
        self.editor.ui.objectPropertyUI.propSY.setValue(state.np.getSy())
        self.editor.ui.objectPropertyUI.propSZ.setValue(state.np.getSz())

        return Task.cont

    def updateObjectTransform(self, event):
        if self.currNodePath is None:
            return

        np = hidden.attachNewNode('temp')
        np.setX(float(self.editor.ui.objectPropertyUI.propX.getValue()))
        np.setY(float(self.editor.ui.objectPropertyUI.propY.getValue()))
        np.setZ(float(self.editor.ui.objectPropertyUI.propZ.getValue()))

        h = float(self.editor.ui.objectPropertyUI.propH.getValue())
        while h < 0:
            h = h + 360.0

        while h > 360:
            h = h - 360.0

        p = float(self.editor.ui.objectPropertyUI.propP.getValue())
        while p < 0:
            p = p + 360.0

        while p > 360:
            p = p - 360.0

        r = float(self.editor.ui.objectPropertyUI.propR.getValue())
        while r < 0:
            r = r + 360.0

        while r > 360:
            r = r - 360.0

        np.setH(h)
        np.setP(p)
        np.setR(r)

        np.setSx(float(self.editor.ui.objectPropertyUI.propSX.getValue()))
        np.setSy(float(self.editor.ui.objectPropertyUI.propSY.getValue()))
        np.setSz(float(self.editor.ui.objectPropertyUI.propSZ.getValue()))

        obj = self.findObjectByNodePath(self.currNodePath)
        action = ActionTransformObj(self.editor, obj[OG.OBJ_UID], Mat4(np.getMat()))
        self.editor.actionMgr.push(action)
        np.remove()
        action()
        self.editor.fNeedToSave = True

    def setObjectTransform(self, uid, xformMat):
        obj = self.findObjectById(uid)
        if obj:
            obj[OG.OBJ_NP].setMat(xformMat)
        self.editor.fNeedToSave = True

    def updateObjectColor(self, r, g, b, a, np=None):
        if np is None:
            np = self.currNodePath

        obj = self.findObjectByNodePath(np)
        if not obj:
            return
        obj[OG.OBJ_RGBA] = (r,g,b,a)
        for child in np.getChildren():
            if not child.hasTag('OBJRoot') and\
               not child.hasTag('_le_sys') and\
               child.getName() != 'bboxLines':
                child.setTransparency(1)
                child.setColorScale(r, g, b, a)
        self.editor.fNeedToSave = True

    def updateObjectModel(self, model, obj, fSelectObject=True):
        """ replace object's model """
        if obj[OG.OBJ_MODEL] != model:
            base.direct.deselectAllCB()

            objNP = obj[OG.OBJ_NP]
            objDef = obj[OG.OBJ_DEF]
            objRGBA = obj[OG.OBJ_RGBA]
            uid = obj[OG.OBJ_UID]

            # load new model
            if objDef.actor:
                try:
                    newobj = Actor(model)
                except Exception:
                    newobj = Actor(Filename.fromOsSpecific(model).getFullpath())
            else:
                newobjModel = base.loader.loadModel(model, okMissing=True)
                if newobjModel is None:
                    print("Can't load model %s"%model)
                    return
                self.flatten(newobjModel, model, objDef, uid)
                newobj = PythonNodePath(newobjModel)
            newobj.setTag('OBJRoot', '1')

            # reparent children
            objNP.findAllMatches("=OBJRoot").reparentTo(newobj)

            # reparent to parent
            newobj.reparentTo(objNP.getParent())

            # copy transform
            newobj.setPos(objNP.getPos())
            newobj.setHpr(objNP.getHpr())
            newobj.setScale(objNP.getScale())

            # copy RGBA data
            self.updateObjectColor(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], newobj)

            # delete old geom
            del self.npIndex[NodePath(objNP)]
            objNP.removeNode()

            # register new geom
            obj[OG.OBJ_NP] = newobj
            obj[OG.OBJ_MODEL] = model
            self.npIndex[NodePath(newobj)] = obj[OG.OBJ_UID]

            # update scene graph label
            self.editor.ui.sceneGraphUI.changeLabel(obj[OG.OBJ_UID], newobj.getName())

            self.editor.fNeedToSave = True
            # update anim if necessary
            animList = obj[OG.OBJ_DEF].animDict.get(model)
            if animList:
                self.updateObjectAnim(animList[0], obj, fSelectObject=fSelectObject)
            else:
                if fSelectObject:
                    base.direct.select(newobj, fUndo=0)

    def updateObjectAnim(self, anim, obj, fSelectObject=True):
        """ replace object's anim """
        if obj[OG.OBJ_ANIM] != anim:
            base.direct.deselectAllCB()
            objNP = obj[OG.OBJ_NP]

            # load new anim
            animName = os.path.basename(anim)
            newAnim = objNP.loadAnims({animName:anim})
            objNP.loop(animName)
            obj[OG.OBJ_ANIM] = anim
            if fSelectObject:
                base.direct.select(objNP, fUndo=0)

            self.editor.fNeedToSave = True

    def updateObjectModelFromUI(self, event, obj):
        """ replace object's model with one selected from UI """
        model = event.GetString()
        if model is not None:
            self.updateObjectModel(model, obj)

    def updateObjectAnimFromUI(self, event, obj):
        """ replace object's anim with one selected from UI """
        anim = event.GetString()
        if anim is not None:
            self.updateObjectAnim(anim, obj)

    def updateObjectProperty(self, event, obj, propName):
        """
        When an obj's property is updated in UI,
        this will update it's value in data structure.
        And call update function if defined.
        """

        objDef = obj[OG.OBJ_DEF]
        objProp = obj[OG.OBJ_PROP]

        propDef = objDef.properties[propName]
        if propDef is None:
            return

        propType = propDef[OG.PROP_TYPE]
        propDataType = propDef[OG.PROP_DATATYPE]

        if propType == OG.PROP_UI_SLIDE:
            if len(propDef) <= OG.PROP_RANGE:
                return

            strVal = event.GetString()
            if strVal == '':
                min = float(propDef[OG.PROP_RANGE][OG.RANGE_MIN])
                max = float(propDef[OG.PROP_RANGE][OG.RANGE_MAX])
                intVal = event.GetInt()
                if intVal is None:
                    return
                val = intVal / 100.0 * (max - min) + min
            else:
                val = strVal

        elif propType == OG.PROP_UI_ENTRY:
            val = event.GetString()

        elif propType == OG.PROP_UI_SPIN:
            val = event.GetInt()

        elif propType == OG.PROP_UI_CHECK:
            if event.GetInt():
                val = True
            else:
                val = False

        elif propType == OG.PROP_UI_RADIO:
            val = event.GetString()

        elif propType == OG.PROP_UI_COMBO:
            val = event.GetString()

        elif propType == OG.PROP_UI_COMBO_DYNAMIC:
            val = event.GetString()

        else:
            # unsupported property type
            return

        # now update object prop value and call update function
        self.updateObjectPropValue(obj, propName, val, \
                                   fSelectObject=(propType != OG.PROP_UI_SLIDE)
                                   )

    def updateObjectPropValue(self, obj, propName, val, fSelectObject=False, fUndo=True):
        """
        Update object property value and
        call update function if defined.
        """
        objDef = obj[OG.OBJ_DEF]
        objProp = obj[OG.OBJ_PROP]

        propDef = objDef.properties[propName]
        propDataType = propDef[OG.PROP_DATATYPE]

        if propDataType != OG.PROP_BLIND:
            val = OG.TYPE_CONV[propDataType](val)
            oldVal = objProp[propName]

            if propDef[OG.PROP_FUNC] is None:
                func = None
                undoFunc = None
            else:
                funcName = propDef[OG.PROP_FUNC][OG.FUNC_NAME]
                funcArgs = propDef[OG.PROP_FUNC][OG.FUNC_ARGS]

                # populate keyword arguments
                kwargs = {}
                undoKwargs = {}
                for key in funcArgs.keys():
                    if funcArgs[key] == OG.ARG_VAL:
                        kwargs[key] = val
                        undoKwargs[key] = oldVal
                    elif funcArgs[key] == OG.ARG_OBJ:
                        undoKwargs[key] = obj
                        objProp[propName] = val
                        kwargs[key] = obj
                    elif funcArgs[key] == OG.ARG_NOLOADING:
                        kwargs[key] = fSelectObject
                        undoKwargs[key] = fSelectObject
                    else:
                        kwargs[key] = funcArgs[key]
                        undoKwargs[key] = funcArgs[key]

                if isinstance(funcName, str):
                    if funcName.startswith('.'):
                        if self.editor:
                            func = Functor(getattr(self.editor, "objectHandler%s"%funcName), **kwargs)
                            undoFunc = Functor(getattr(self.editor, "objectHandler%s"%funcName), **undoKwargs)
                        else: # when loaded outside of LE
                            func = Functor(getattr(base, "objectHandler%s"%funcName), **kwargs)
                            undoFunc = Functor(getattr(base, ".objectHandler%s"%funcName), **undoKwargs)
                    else:
                        func = Functor(eval(funcName), **kwargs)
                        undoFunc = Functor(eval(funcName), **undoKwargs)
                else:
                    func = Functor(funcName, **kwargs)
                    undoFunc = Functor(funcName, **undoKwargs)

                # finally call update function
                #func(**kwargs)
        else:
            oldVal = objProp[propName]
            func = None
            undoFunc = None
        action = ActionUpdateObjectProp(self.editor, fSelectObject, obj, propName, val, oldVal, func, undoFunc)
        if fUndo:
            self.editor.actionMgr.push(action)
        action()

        if self.editor:
            self.editor.fNeedToSave = True
            if fSelectObject:
                base.direct.select(obj[OG.OBJ_NP], fUndo=0)

    def updateCurve(self, val, obj):
        curve = obj[OG.OBJ_NP]
        degree = int(val)
        curveNode = obj[OG.OBJ_PROP]['curveInfo']
        curveInfor = []
        for item in curveNode:
            curveInfor.append((None, item[1].getPos()))
        curve.setup(degree, curveInfor)

    def updateObjectProperties(self, nodePath, propValues):
        """
        When a saved level is loaded,
        update an object's properties
        And call update function if defined.
        """
        obj = self.findObjectByNodePath(nodePath)

        if obj:
            for propName in propValues:
                self.updateObjectPropValue(obj, propName, propValues[propName])

    def traverse(self, parent, parentId = None):
        """
        Trasverse scene graph to gather data for saving
        """
        for child in parent.getChildren():
            if child.hasTag('OBJRoot') and not child.hasTag('Controller'):
                obj = self.findObjectByNodePath(child)

                if obj:
                    uid = obj[OG.OBJ_UID]
                    np = obj[OG.OBJ_NP]
                    objDef = obj[OG.OBJ_DEF]
                    objModel = obj[OG.OBJ_MODEL]
                    objAnim = obj[OG.OBJ_ANIM]
                    objProp = obj[OG.OBJ_PROP]
                    objRGBA = obj[OG.OBJ_RGBA]

                    if parentId:
                        parentStr = "objects['%s']"%parentId
                    else:
                        parentStr = "None"

                    if objModel:
                        modelStr = "'%s'"%objModel
                    else:
                        modelStr = "None"

                    if objAnim:
                        animStr = "'%s'"%objAnim
                    else:
                        animStr = "None"

                    if objDef.named:
                        nameStr = "'%s'"%np.getName()
                    else:
                        nameStr = "None"

                    if objDef.name == '__Curve__':
                        #transfer the curve information from control nodes into simple positions for file save
                        objCurveInfo = obj[OG.OBJ_PROP]['curveInfo']
                        self.objDegree = obj[OG.OBJ_PROP]['Degree']
                        newobjCurveInfo = []
                        for item in objCurveInfo:
                            newobjCurveInfo.append((item[0], item[1].getPos()))

                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewCurveFromFile(%s, %s, '%s', %s, False, None)"%(uid, newobjCurveInfo, self.objDegree, uid, parentStr))
                    else:
                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', %s, %s, %s, False, None, %s)"%(uid, objDef.name, uid, modelStr, parentStr, animStr, nameStr))

                    self.saveData.append("if objects['%s']:"%uid)
                    self.saveData.append("    objects['%s'].setPos(%s)"%(uid, np.getPos()))
                    self.saveData.append("    objects['%s'].setHpr(%s)"%(uid, np.getHpr()))
                    self.saveData.append("    objects['%s'].setScale(%s)"%(uid, np.getScale()))
                    self.saveData.append("    objectMgr.updateObjectColor(%f, %f, %f, %f, objects['%s'])"%(objRGBA[0], objRGBA[1], objRGBA[2], objRGBA[3], uid))

                    if objDef.name == '__Curve__':
                        pass
                    else:
                        self.saveData.append("    objectMgr.updateObjectProperties(objects['%s'], %s)"%(uid,objProp))

                self.traverse(child, uid)

    def getSaveData(self):
        self.saveData = []
        self.getPreSaveData()
        self.traverse(base.render)
        self.getPostSaveData()
        return self.saveData

    def getPreSaveData(self):
        """
        if there are additional data to be saved before main data
        you can override this function to populate data
        """

    def getPostSaveData(self):
        """
        if there are additional data to be saved after main data
        you can override this function to populate data
        """

    def duplicateObject(self, nodePath, parent=None):
        obj = self.findObjectByNodePath(nodePath)
        if obj is None:
            return None
        objDef = obj[OG.OBJ_DEF]
        objModel = obj[OG.OBJ_MODEL]
        objAnim = obj[OG.OBJ_ANIM]
        objRGBA = obj[OG.OBJ_RGBA]

        if parent is None:
            parentNP = nodePath.getParent()
            parentObj = self.findObjectByNodePath(parentNP)
            if parentObj is None:
                parent = parentNP
            else:
                parent = parentObj[OG.OBJ_NP]

        newObjNP = self.addNewObject(objDef.name, parent=parent, fSelectObject = False)

        # copy transform data
        newObjNP.setPos(obj[OG.OBJ_NP].getPos())
        newObjNP.setHpr(obj[OG.OBJ_NP].getHpr())
        newObjNP.setScale(obj[OG.OBJ_NP].getScale())

        newObj = self.findObjectByNodePath(NodePath(newObjNP))
        if newObj is None:
            return None
        # copy model info
        self.updateObjectModel(obj[OG.OBJ_MODEL], newObj, fSelectObject=False)

        # copy anim info
        self.updateObjectAnim(obj[OG.OBJ_ANIM], newObj, fSelectObject=False)

        # copy other properties
        for key in obj[OG.OBJ_PROP]:
            self.updateObjectPropValue(newObj, key, obj[OG.OBJ_PROP][key])
        return newObjNP

    def duplicateChild(self, nodePath, parent):
        children = nodePath.findAllMatches('=OBJRoot')
        for childNP in children:
            newChildObjNP = self.duplicateObject(childNP, parent)
            if newChildObjNP is not None:
                self.duplicateChild(childNP, newChildObjNP)

    def duplicateSelected(self):
        selectedNPs = base.direct.selected.getSelectedAsList()
        duplicatedNPs = []
        for nodePath in selectedNPs:
            newObjNP = self.duplicateObject(nodePath)
            if newObjNP is not None:
                self.duplicateChild(nodePath, newObjNP)
                duplicatedNPs.append(newObjNP)

        base.direct.deselectAllCB()
        for newNodePath in duplicatedNPs:
            base.direct.select(newNodePath, fMultiSelect = 1, fUndo=0)

        self.editor.fNeedToSave = True

    def makeSelectedLive(self):
        obj = self.findObjectByNodePath(base.direct.selected.last)
        if obj:
            if self.currLiveNP:
                self.currLiveNP.clearColorScale()
                if self.currLiveNP == obj[OG.OBJ_NP]:
                    self.currLiveNP = None
                    return

            self.currLiveNP = obj[OG.OBJ_NP]
            self.currLiveNP.setColorScale(0, 1, 0, 1)

    def replaceObjectWithTypeName(self, obj, typeName):
        uid = obj[OG.OBJ_UID]
        objNP = obj[OG.OBJ_NP]
        mat = objNP.getMat()
        parentObj = self.findObjectByNodePath(objNP.getParent())
        if parentObj:
            parentNP = parentObj[OG.OBJ_NP]
        else:
            parentNP = None
        self.removeObjectById(uid)
        self.editor.ui.sceneGraphUI.delete(uid)
        newobj = self.addNewObject(typeName, uid, parent=parentNP, fSelectObject=False)
        newobj.setMat(mat)

    def flatten(self, newobjModel, model, objDef, uid):
        # override this to flatten models
        pass

    def findActors(self, parent):
        for child in parent.getChildren():
            if child.hasTag('OBJRoot') and not child.hasTag('Controller'):
                obj = self.findObjectByNodePath(child)

                if obj:
                    if isinstance(obj[OG.OBJ_NP],Actor):
                        self.Actor.append(obj)

                self.findActors(child)

    def findNodes(self, parent):
        for child in parent.getChildren():
            if child.hasTag('OBJRoot') and not child.hasTag('Controller'):
                obj = self.findObjectByNodePath(child)

                if obj:
                    self.Nodes.append(obj)

                self.findActors(child)
